using System;
using System.Collections.Generic;
using System.Diagnostics.Contracts;
using System.Linq;

namespace ReactiveLang
{
    using Util;

    public interface IStatement<out T> : INode<IStatementParent<T>>
    {
    }

    public static partial class Statement
    {
        #region Create

        class AnonymousStatement<T> : IStatement<T>
        {
            internal Func<IStatementParent<T>, IDisposable> _activate;

            public IDisposable Activate(IStatementParent<T> parent)
            {
                return _activate(parent);
            }
        }
        public static IStatement<T> CreateWithDisposable<T>(Func<IStatementParent<T>, IDisposable> activate)
        {
            Contract.Requires<ArgumentNullException>(activate != null, "activate");
            return new AnonymousStatement<T> { _activate = activate };
        }
        public static IStatement<T> Create<T>(Func<IStatementParent<T>, Action> activate)
        {
            Contract.Requires<ArgumentNullException>(activate != null, "activate");
            return CreateWithDisposable<T>(p => Disposable.Create(activate(p)));
        }

        #endregion

        #region Activate

        public static IDisposable Activate<T>(this IStatement<T> stmt,
            Action<Exception> onException, Func<IExpression<T>, IDisposable> onReturn,
            Func<IDisposable> onBreak, Func<IDisposable> onContinue)
        {
            Contract.Requires<ArgumentNullException>(stmt != null, "stmt");
            Contract.Requires<ArgumentNullException>(onException != null, "onException");
            Contract.Requires<ArgumentNullException>(onReturn != null, "onReturn");
            Contract.Requires<ArgumentNullException>(onBreak != null, "onBreak");
            Contract.Requires<ArgumentNullException>(onContinue != null, "onContinue");
            return stmt.Activate(StatementParent.Create(onException, onReturn, onBreak, onContinue));
        }
        internal static IDisposable ActivateDerived<TReturn>(this IStatement<TReturn> stmt, IStatementParent<TReturn> grandParent,
            Action<Exception> onException = null, Func<IExpression<TReturn>, IDisposable> onReturn = null,
            Func<IDisposable> onBreak = null, Func<IDisposable> onContinue = null)
        {
            Contract.Requires<ArgumentNullException>(stmt != null, "stmt");
            Contract.Requires<ArgumentNullException>(grandParent != null, "grandParent");
            return stmt.Activate(grandParent.Derive(onException, onReturn, onBreak, onContinue));
        } 
        #endregion

        public static IStatement<T> If<T>(this IExpression<bool> condition, IStatement<T> trueStatement, IStatement<T> falseStatement)
        {
            Contract.Requires<ArgumentNullException>(condition != null, "condition");
            return Create<T>(p =>
            {
                var bodyHandle = Disposable.Empty;
                var conditionHandle = condition.Distinct().ActivateDerived(p, x =>
                {
                    bodyHandle.Dispose();
                    var activeStmt = x ? trueStatement : falseStatement;
                    bodyHandle = (activeStmt != null) ? activeStmt.ActivateDerived(p) : Disposable.Empty;
                });
                return delegate
                {
                    bodyHandle.Dispose();
                    conditionHandle.Dispose();
                };
            });
        }

        public static IStatement<T> Return<T>(this IExpression<T> value)
        {
            Contract.Requires<ArgumentNullException>(value != null, "value");
            return CreateWithDisposable<T>(p => p.OnReturn(value));
        }

        public static IStatement<T> Block<T>(params IStatement<T>[] statements)
        {
            Contract.Requires<ArgumentNullException>(statements != null, "statements");
            return CreateWithDisposable<T>(p =>
            {
                var collection = new StatementCollection<T>(p);
                foreach (var stmt in statements)
                    collection.Add(stmt);
                return collection;
            });
        }

        public static IStatement<TReturn> Imperative<TArg, TReturn>(IExpression<TArg> arg, Action<TArg> @do)
        {
            Contract.Requires<ArgumentNullException>(arg != null, "arg");
            Contract.Requires<ArgumentNullException>(@do != null, "@do");
            return CreateWithDisposable<TReturn>(p => arg.ActivateDerived(p, @do));
        }

        public static IStatement<TReturn> AsStatement<TReturn, TValue>(this IExpression<TValue> expression)
        {
            Contract.Requires<ArgumentNullException>(expression != null, "expression");
            return CreateWithDisposable<TReturn>(p => expression.ActivateDerived(p, delegate { }));
        }
    }
}